#include <future>
#include <optional>

#include "kvstore.pb.h"
#include "netlib/base/logger.h"
#include "netlib/net/event/event_loop_thread.h"
#include "netlib/rpc/rpc_client.h"

namespace netlib::examples {

class KVStoreClient : public NonCopyable {
public:
	KVStoreClient(net::EventLoop* loop,
	              const net::InetAddress& addr,
	              std::string&& name = "KVStoreClient") :
	    client_(loop, addr, std::move(name)) {}

	void Connect() { client_.Connect(); }
	bool IsConnected() { return client_.IsConnected(); }

	void Put(const std::string& key, const std::string& value) {
		KVStoreService::Stub stub(client_.GetRpcChannel());
		PutRequest request;
		request.set_key(key);
		request.set_value(value);
		Response response;
		std::promise<bool> promise;
		auto future = promise.get_future();
		auto done =
		    ::google::protobuf::NewCallback(&KVStoreClient::DoneCallback, &response, &promise);
		stub.Put(nullptr, &request, &response, done);
		if (!future.get()) {
			assert(response.has_message());
			LOG_ERROR << "Failed to put " << key << " " << value
			          << ", message: " << response.message();
		}
	}
	auto Get(const std::string& key) -> std::optional<std::string> {
		KVStoreService::Stub stub(client_.GetRpcChannel());
		GetRequest request;
		request.set_key(key);
		Response response;
		std::promise<bool> promise;
		auto future = promise.get_future();
		auto done =
		    ::google::protobuf::NewCallback(&KVStoreClient::DoneCallback, &response, &promise);
		stub.Get(nullptr, &request, &response, done);
		if (!future.get()) {
			assert(response.has_message());
			LOG_ERROR << "Failed to get " << key << ", message: " << response.message();
			return std::nullopt;
		}
		assert(response.has_value());
		return response.value();
	}

private:
	static void DoneCallback(Response* resq, std::promise<bool>* promise) {
		promise->set_value(resq->success());
	}
	rpc::RpcClient client_;
};

} // namespace netlib::examples

int main() {
	using namespace netlib;
	net::InetAddress addr(13589);
	net::EventLoopThread loop_thread;
	examples::KVStoreClient client(loop_thread.StartLoop(), addr);
	client.Connect();
	if (!client.IsConnected()) {
		LOG_FATAL << "Failed to connect to server";
	}

	client.Put("test-key", "test-value");
	auto res = client.Get("test-key");
	assert(res.has_value());
	assert(res.value() == "test-value");
}